use std::io::Cursor;

use anyhow::Error;
use log::{debug, error};
use quick_xml::{
    events::{BytesStart, Event},
    name::{Namespace, QName, ResolveResult},
    NsReader, Writer,
};

use crate::{ietf::NS_IETF_NETCONF, Capability, NcMsg, NcOpType};

#[derive(Debug, PartialEq)]
pub struct Hello {
    pub session_id: u32,
    pub capabilities: Vec<Capability>,
}

fn decode_ncmsg(data: &str) -> Result<NcMsg, anyhow::Error> {
    let mut reader = NsReader::from_str(data.trim());
    reader.config_mut().trim_text(true);
    match reader.read_resolved_event()? {
        (ResolveResult::Unbound, Event::Start(bs)) => {
            if bs.name().eq(&QName(b"hello")) {
                // hello message
                let content = reader.read_text(bs.to_end().name())?;
                return decode_hello(&content);
            } else {
                return Err(Error::msg(format!("invalid netconf msg: {}", data)));
            }
        }
        (
            ResolveResult::Bound(Namespace(b"urn:ietf:params:xml:ns:netconf:base:1.0")),
            Event::Start(bs),
        ) => {
            if bs.name().eq(&QName(b"rpc")) {
                // rpc message
                let message_id = String::from_utf8(
                    bs.try_get_attribute("message-id")?
                        .expect("message-id is empty")
                        .value
                        .to_vec(),
                )?;

                let content = reader.read_text(bs.to_end().name())?;
                return decode_rpc(&content, message_id);
            } else {
                return Err(Error::msg(format!("invalid netconf msg: {}", data)));
            }
        }
        _ => Err(Error::msg("invalid nc msg")),
    }
}

fn decode_hello(d: &str) -> Result<NcMsg, anyhow::Error> {
    let mut reader = NsReader::from_str(d);
    reader.config_mut().trim_text(true);
    let mut caps = vec![];
    let mut session_id = None;
    let mut flag = 0u8;
    while flag.ne(&3) {
        match reader.read_event()? {
            Event::Start(bs) => {
                if bs.name().eq(&QName(b"capabilities")) {
                    let content = reader.read_text(bs.to_end().name())?;
                    let mut r = NsReader::from_str(&content);
                    r.config_mut().trim_text(true);
                    loop {
                        match r.read_event() {
                            Ok(Event::Start(bs)) => {
                                if bs.name().eq(&QName(b"capability")) {
                                    let capability = r.read_text(bs.to_end().name())?;
                                    caps.push(capability.trim().to_string());
                                }
                            }
                            Ok(Event::Eof) => break,
                            Err(e) => {
                                error!("decode hello failed: {}", e.to_string());
                            }
                            _ => (),
                        }
                    }
                    flag |= 1;
                } else if bs.name().eq(&QName(b"session-id")) {
                    session_id = Some(
                        reader
                            .read_text(bs.to_end().name())
                            .unwrap()
                            .parse::<u32>()
                            .unwrap(),
                    );
                    flag |= 2;
                }
            }
            Event::Eof => break,
            _ => (),
        }
    }

    Ok(NcMsg::Hello(session_id, Box::new(caps)))
}

fn decode_rpc(content: &str, message_id: String) -> Result<NcMsg, anyhow::Error> {
    let mut reader = NsReader::from_str(content);
    reader.config_mut().trim_text(true);
    match reader.read_resolved_event()? {
        (ResolveResult::Bound(Namespace(NS_IETF_NETCONF)), evt) => {
            // netconf message
            let rpc_name = get_rpc_name_from_reader(&evt);
            let ns = String::from_utf8(NS_IETF_NETCONF.to_vec())?;
            decode_nc_rpc(&content, message_id, rpc_name, ns)
        }
        (ResolveResult::Bound(ns), evt) => {
            // other rpc
            let rpc_name = get_rpc_name_from_reader(&evt);
            Ok(NcMsg::Rpc(
                message_id,
                String::from_utf8(ns.into_inner().to_vec())?,
                rpc_name,
                content.to_string(),
            ))
        }
        (ResolveResult::Unbound, evt) => {
            // netconf message
            let rpc_name = get_rpc_name_from_reader(&evt);
            let ns = String::from_utf8(NS_IETF_NETCONF.to_vec())?;
            decode_nc_rpc(&content, message_id, rpc_name, ns)
        }
        _ => Err(Error::msg("devode rpc failed")),
    }
}

fn get_rpc_name_from_reader(evt: &Event<'_>) -> String {
    match evt {
        Event::Empty(bs) => String::from_utf8(bs.name().into_inner().to_vec()).unwrap_or_default(),
        Event::Start(bs) => String::from_utf8(bs.name().into_inner().to_vec()).unwrap_or_default(),
        _ => "".to_string(),
    }
}

fn decode_nc_rpc(
    content: &str,
    message_id: String,
    rpc_name: String,
    ns: String,
) -> Result<NcMsg, anyhow::Error> {
    let content = fix_namespace(content, &ns)?;
    match rpc_name.as_str() {
        "get-config" => Ok(NcMsg::NcOp(message_id, NcOpType::GetConfig(content))),
        "edit-config" => Ok(NcMsg::NcOp(message_id, NcOpType::EditConfig)),
        "copy-config" => Ok(NcMsg::NcOp(message_id, NcOpType::CopyConfig)),
        "delete-config" => Ok(NcMsg::NcOp(message_id, NcOpType::DeleteConfig)),
        "lock" => Ok(NcMsg::NcOp(message_id, NcOpType::Lock)),
        "unlock" => Ok(NcMsg::NcOp(message_id, NcOpType::Unlock)),
        "get" => Ok(NcMsg::NcOp(message_id, NcOpType::Get)),
        "close-session" => Ok(NcMsg::NcOp(message_id, NcOpType::CloseSession)),
        "kill-session" => Ok(NcMsg::NcOp(message_id, NcOpType::KillSession)),
        "commit" => Ok(NcMsg::NcOp(message_id, NcOpType::Commit)),
        "discard-changes" => Ok(NcMsg::NcOp(message_id, NcOpType::DiscardChanges)),
        "cancel-commit" => Ok(NcMsg::NcOp(message_id, NcOpType::CancelCommit)),
        "validate" => Ok(NcMsg::NcOp(message_id, NcOpType::Validate)),
        _ => Err(Error::msg("devode rpc failed")),
    }
}

fn fix_namespace(content: &str, ns: &str) -> Result<String, Error> {
    let mut reader = NsReader::from_str(content);
    reader.config_mut().trim_text(true);
    let mut writer = Writer::new(Cursor::new(Vec::new()));
    let (rr, event) = reader.read_resolved_event()?;
    if let ResolveResult::Unbound = rr {
        match event {
            Event::Start(bs) => {
                let tag_name = String::from_utf8(bs.name().into_inner().to_vec())?;
                let mut bs = BytesStart::new(tag_name.as_str());
                bs.push_attribute(("xmlns", ns));
                writer.write_event(Event::Start(bs))?;
            }
            Event::Empty(bs) => {
                let tag_name = String::from_utf8(bs.name().into_inner().to_vec())?;
                let mut bs = BytesStart::new(tag_name.as_str());
                bs.push_attribute(("xmlns", ns));
                writer.write_event(Event::Empty(bs))?;
            }
            _ => (),
        }
    } else {
        writer.write_event(event)?;
    }

    loop {
        match reader.read_event()? {
            Event::Eof => break,
            e => writer.write_event(e)?,
        }
    }

    Ok(String::from_utf8(
        writer.into_inner().into_inner().to_vec(),
    )?)
}

pub fn decode(data: &[u8]) -> (usize, Option<Vec<crate::NcMsg>>) {
    let mut pos: usize = 0;
    let mut msgs = vec![];
    let mut d = vec![];
    for i in 0..data.len() {
        match data[i] {
            0x0A => {
                if i + 4 <= data.len() {
                    if data[i..i + 4].eq(&[0x0A, 0x23, 0x23, 0x0A]) {
                        pos = i + 4;
                        let ds = String::from_utf8(d.clone()).unwrap();
                        d.clear();
                        match decode_ncmsg(&ds) {
                            Ok(msg) => msgs.push(msg),
                            Err(e) => {
                                error!("decode msg error: {}", e.to_string());
                            }
                        }
                        debug!("decode nc msg: {}", ds);
                    }
                } else {
                    break;
                }
                if i == data.len() {
                    continue;
                }
                if data[i + 1].eq(&0x23) {
                    for j in i + 1..data.len() {
                        if data[j].eq(&0x0A) {
                            let len = String::from_utf8(data[i + 1..j].to_vec())
                                .unwrap()
                                .parse::<usize>()
                                .unwrap();
                            if j + len > data.len() {
                                d.append(&mut data[j..len].to_vec());
                            }
                        } else if data[j] < 0x30 || data[j] > 0x39 {
                            break;
                        }
                    }
                }
            }
            0x5D => {
                if i + 6 < data.len() {
                    if data[i..i + 6].eq(&[0x5D, 0x5D, 0x3E, 0x5D, 0x5D, 0x3E]) {
                        pos = i + 6;
                        let ds = String::from_utf8(data[0..i].to_vec()).unwrap();
                        match decode_ncmsg(&ds) {
                            Ok(msg) => msgs.push(msg),
                            Err(e) => {
                                error!("decode msg error: {}", e.to_string());
                            }
                        }
                        debug!("decode nc msg: {}", ds);
                    }
                }
            }
            _ => (),
        }
    }
    (pos, Some(msgs))
}

pub fn encode(msg: crate::NcMsg) -> Vec<u8> {
    match msg {
        NcMsg::Hello(Some(session_id), _caps) => format!(
            "r#
            <hello>
          <capabilities>
            <capability>
              urn:ietf:params:xml:ns:netconf:base:1.0
            </capability>
            <capability>
              urn:ietf:params:ns:netconf:capability:startup:1.0
            </capability>
          </capabilities>
          <session-id>{}</session-id>
        </hello>
            #",
            session_id
        )
        .as_bytes()
        .to_vec(),
        NcMsg::Hello(None, _caps) => b"r#
            <hello>
          <capabilities>
            <capability>
              urn:ietf:params:xml:ns:netconf:base:1.0
            </capability>
            <capability>
              urn:ietf:params:ns:netconf:capability:startup:1.0
            </capability>
          </capabilities>
        </hello>
            #"
        .to_vec(),
        _ => vec![],
    }
}

#[cfg(test)]
mod tests {
    use anyhow::Error;

    use crate::codec::decode_ncmsg;

    #[test]
    fn test_decode_hello() -> Result<(), anyhow::Error> {
        let content = r#"
<hello>
        <capabilities>
        <capability>
        urn:ietf:params:xml:ns:netconf:base:1.0
        </capability>
        </capabilities>
        <session-id>4</session-id>
</hello>
"#;
        let hello = decode_ncmsg(&content);
        println!("{:#?}", hello);

        let content = r#"
<hello>
      <capabilities>
        <capability>
          urn:ietf:params:xml:ns:netconf:base:1.0
        </capability>
        <capability>
          urn:ietf:params:ns:netconf:capability:startup:1.0
        </capability>
      </capabilities>
</hello>
        "#;
        let hello = decode_ncmsg(&content);
        println!("{:#?}", hello);
        Ok(())
    }

    #[test]
    fn test_decode_msg() -> Result<(), Error> {
        let msg = decode_ncmsg(
            r#"
    <rpc message-id="105"
    xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
      <get-config>
        <source><running/></source>
        <config xmlns="http://example.com/schema/1.2/config">
         <users/>
        </config>
      </get-config>
    </rpc>
        "#,
        )?;
        println!("{:?}", msg);
        Ok(())
    }
}
